#include "qe_log.h"
#include "qe_time.h"
#include "qe_memory.h"
#include "qe_assert.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-hwicap");



#define HWICAP_PACKET_DUMMY     0xFFFFFFFF
#define HWICAP_PACKET_SYNC      0xAA995566
#define HWICAP_PACKET_NO_OP     0x20000000
#define HWICAP_PACKET_WBSTAR    0x30020001
#define HWICAP_PACKET_W2CMD     0x30008001
#define HWICAP_PACKET_SPI_CFG   0x3003e001
#define HWICAP_PACKET_SPI_QUAD  0x0000026c
#define HWICAP_PACKET_IPROG     0x0000000F

typedef struct
{
    volatile qe_u32 reserved0[64];
    volatile qe_u32 wdfifo;
    volatile qe_u32 reserved1[2];
    volatile qe_u32 cr;             /* Control Register */
    volatile qe_u32 sr;             /* Status Register */
} axi_hwicap_reg;

typedef struct
{
    qe_hwboot_dev hwboot;
    axi_hwicap_reg *reg;
    qe_uint spi_buswidth;
    qe_uint num_packets;
    qe_uint addr_pkt_offs;
    qe_u32 packets[16];
} axi_hwicap_dev;

/**
 * Example Bitstream for IPROG through ICAPE2
 * Reference 7 Serial FPGAs Configuration User Guide page 137
 * 
 * Configuration Data(hex)      Explanation
 * FFFFFFFF                     Dummy Word
 * AA995566                     Sync Word
 * 20000000                     Type 1 NO OP
 * 30020001                     Type 1 Write 1 Words to WBSTAR
 * 00000000                     Warm Boot Start Address (Load the Desired Address)
 * 30008001                     Type 1 Write 1 Words to CMD
 * 0000000f                     IPROG Command
 * 20000000                     Type 1 NO OP
 */

static inline void axi_hwicap_packet_append(axi_hwicap_dev *dev, qe_u32 packet)
{
    dev->packets[dev->num_packets++] = packet;
}

static void axi_hwicap_packet_init(axi_hwicap_dev *dev)
{
    dev->num_packets = 0;

    axi_hwicap_packet_append(dev, HWICAP_PACKET_DUMMY);
    axi_hwicap_packet_append(dev, HWICAP_PACKET_SYNC);
    axi_hwicap_packet_append(dev, HWICAP_PACKET_NO_OP);

    if (dev->spi_buswidth == 4) {
        axi_hwicap_packet_append(dev, HWICAP_PACKET_SPI_CFG);
        axi_hwicap_packet_append(dev, HWICAP_PACKET_SPI_QUAD);
    }

    axi_hwicap_packet_append(dev, HWICAP_PACKET_WBSTAR);
    dev->addr_pkt_offs = dev->num_packets;
    axi_hwicap_packet_append(dev, 0x00000000);

    axi_hwicap_packet_append(dev, HWICAP_PACKET_W2CMD);
    axi_hwicap_packet_append(dev, HWICAP_PACKET_IPROG);
    axi_hwicap_packet_append(dev, HWICAP_PACKET_NO_OP);

    qe_debug("num packets %d", dev->num_packets);
}

static void axi_hwicap_boot(qe_hwboot_dev *dev, qe_ubase addr)
{
    int i;
    axi_hwicap_dev *hwicap = (axi_hwicap_dev *)qe_hwboot_dev_priv(dev);

    qe_notice("boot %x", addr);

    hwicap->packets[hwicap->addr_pkt_offs] = addr >> 8;

    qe_debug("%x num packets %d", hwicap, hwicap->num_packets);
    for (i=0; i<hwicap->num_packets; i++) {
        qe_debug("packets[%d] %x", i, hwicap->packets[i]);
    }

    for (i=0; i<hwicap->num_packets; i++) {
        hwicap->reg->wdfifo = hwicap->packets[i];
        hwicap->reg->cr = 1;
        qe_usleep(100);
    }
}

static qe_hwboot_ops axi_hwicap_ops = {
    .boot = axi_hwicap_boot,
};

static qe_ret axi_hwicap_probe(const void *fdt, int offset)
{
    int len;
    const char *name;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_spi_width;
    axi_hwicap_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    prop_spi_width = (const qe_u32 *)fdt_getprop(fdt, offset, "spi-buswidth", &len);
    if (!prop_spi_width) {
        qe_error("%s no spi-buswidth prop", name);
        return qe_err_param;
    }

    dev = qe_malloc(sizeof(axi_hwicap_dev));
    qe_assert(dev);

    dev->reg = (axi_hwicap_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->spi_buswidth = fdt32_to_cpu(*prop_spi_width);

    axi_hwicap_packet_init(dev);

    qe_hwboot_register(&dev->hwboot, name, &axi_hwicap_ops, dev);

    qe_debug("register %s %x", name, dev);

    return qe_ok;
}

static qe_device_id axi_hwicap_ids[] = {
    {.compatible="xlnx,axi-hwicap"},
    {},
};

QE_DRIVER(axi_hwicap) = {
    .name = "axi-hwicap",
    .of_match = axi_hwicap_ids,
    .probe = axi_hwicap_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_hwicap);
